// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/audio_renderer_mixer.h"

#include <cmath>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_timestamp_helper.h"

namespace media {

enum { kPauseDelaySeconds = 10 };

// Tracks the maximum value of a counter and logs it into a UMA histogram upon
// each increase of the maximum. NOT thread-safe, make sure it is used under
// lock.
class AudioRendererMixer::UMAMaxValueTracker {
public:
    UMAMaxValueTracker(const UmaLogCallback& log_callback)
        : log_callback_(log_callback)
        , count_(0)
        , max_count_(0)
    {
    }

    ~UMAMaxValueTracker() { }

    // Increments the counter, updates the maximum.
    void Increment()
    {
        ++count_;
        if (max_count_ < count_) {
            max_count_ = count_;
            log_callback_.Run(max_count_);
        }
    }

    // Decrements the counter.
    void Decrement()
    {
        DCHECK_GE(count_, 0);
        --count_;
    }

private:
    const UmaLogCallback log_callback_;
    int count_;
    int max_count_;
    DISALLOW_COPY_AND_ASSIGN(UMAMaxValueTracker);
};

AudioRendererMixer::AudioRendererMixer(const AudioParameters& output_params,
    scoped_refptr<AudioRendererSink> sink,
    const UmaLogCallback& log_callback)
    : output_params_(output_params)
    , audio_sink_(std::move(sink))
    , master_converter_(output_params, output_params, true)
    , pause_delay_(base::TimeDelta::FromSeconds(kPauseDelaySeconds))
    , last_play_time_(base::TimeTicks::Now())
    ,
    // Initialize |playing_| to true since Start() results in an auto-play.
    playing_(true)
    , input_count_tracker_(new UMAMaxValueTracker(log_callback))
{
    DCHECK(audio_sink_);
    audio_sink_->Initialize(output_params, this);
    audio_sink_->Start();
}

AudioRendererMixer::~AudioRendererMixer()
{
    // AudioRendererSink must be stopped before mixer is destructed.
    audio_sink_->Stop();

    // Ensure that all mixer inputs have removed themselves prior to destruction.
    DCHECK(master_converter_.empty());
    DCHECK(converters_.empty());
    DCHECK_EQ(error_callbacks_.size(), 0U);
}

void AudioRendererMixer::AddMixerInput(const AudioParameters& input_params,
    AudioConverter::InputCallback* input)
{
    base::AutoLock auto_lock(lock_);
    if (!playing_) {
        playing_ = true;
        last_play_time_ = base::TimeTicks::Now();
        audio_sink_->Play();
    }

    int input_sample_rate = input_params.sample_rate();
    if (is_master_sample_rate(input_sample_rate)) {
        master_converter_.AddInput(input);
    } else {
        AudioConvertersMap::iterator converter = converters_.find(input_sample_rate);
        if (converter == converters_.end()) {
            std::pair<AudioConvertersMap::iterator, bool> result = converters_.insert(std::make_pair(
                input_sample_rate, base::WrapUnique(
                                       // We expect all InputCallbacks to be
                                       // capable of handling arbitrary buffer
                                       // size requests, disabling FIFO.
                                       new LoopbackAudioConverter(input_params, output_params_, true))));
            converter = result.first;

            // Add newly-created resampler as an input to the master mixer.
            master_converter_.AddInput(converter->second.get());
        }
        converter->second->AddInput(input);
    }

    input_count_tracker_->Increment();
}

void AudioRendererMixer::RemoveMixerInput(
    const AudioParameters& input_params,
    AudioConverter::InputCallback* input)
{
    base::AutoLock auto_lock(lock_);

    int input_sample_rate = input_params.sample_rate();
    if (is_master_sample_rate(input_sample_rate)) {
        master_converter_.RemoveInput(input);
    } else {
        AudioConvertersMap::iterator converter = converters_.find(input_sample_rate);
        DCHECK(converter != converters_.end());
        converter->second->RemoveInput(input);
        if (converter->second->empty()) {
            // Remove converter when it's empty.
            master_converter_.RemoveInput(converter->second.get());
            converters_.erase(converter);
        }
    }

    input_count_tracker_->Decrement();
}

void AudioRendererMixer::AddErrorCallback(const base::Closure& error_cb)
{
    base::AutoLock auto_lock(lock_);
    error_callbacks_.push_back(error_cb);
}

void AudioRendererMixer::RemoveErrorCallback(const base::Closure& error_cb)
{
    base::AutoLock auto_lock(lock_);
    for (ErrorCallbackList::iterator it = error_callbacks_.begin();
         it != error_callbacks_.end();
         ++it) {
        if (it->Equals(error_cb)) {
            error_callbacks_.erase(it);
            return;
        }
    }

    // An error callback should always exist when called.
    NOTREACHED();
}

OutputDeviceInfo AudioRendererMixer::GetOutputDeviceInfo()
{
    DVLOG(1) << __func__;
    return audio_sink_->GetOutputDeviceInfo();
}

bool AudioRendererMixer::CurrentThreadIsRenderingThread()
{
    return audio_sink_->CurrentThreadIsRenderingThread();
}

int AudioRendererMixer::Render(base::TimeDelta delay,
    base::TimeTicks delay_timestamp,
    int prior_frames_skipped,
    AudioBus* audio_bus)
{
    TRACE_EVENT0("audio", "AudioRendererMixer::Render");
    base::AutoLock auto_lock(lock_);

    // If there are no mixer inputs and we haven't seen one for a while, pause the
    // sink to avoid wasting resources when media elements are present but remain
    // in the pause state.
    const base::TimeTicks now = base::TimeTicks::Now();
    if (!master_converter_.empty()) {
        last_play_time_ = now;
    } else if (now - last_play_time_ >= pause_delay_ && playing_) {
        audio_sink_->Pause();
        playing_ = false;
    }

    uint32_t frames_delayed = AudioTimestampHelper::TimeToFrames(delay, output_params_.sample_rate());
    master_converter_.ConvertWithDelay(frames_delayed, audio_bus);
    return audio_bus->frames();
}

void AudioRendererMixer::OnRenderError()
{
    // Call each mixer input and signal an error.
    base::AutoLock auto_lock(lock_);
    for (const auto& cb : error_callbacks_)
        cb.Run();
}

} // namespace media
